﻿' TouchFlo Detacher
' Copyright (C) 2009, Avian Waves Software
' SEE LICENSE.TXT for software licensing information regarding original TouchFlo Detacher source code and binaries
' SEE OpenNetCF_LICENSE.TXT for licensing information regarding OpenNetCF binaries

Imports TouchFloDetacher.Settings
Imports OpenNETCF.ToolHelp
Imports System.Text

Public Class TFDetachSvc
  ' Private member variables
  Private _isLoaded As Boolean = False  ' Is the form finished loading?
  ' Private _subclass As SubClass         ' The subclass that will handle all custom messages

  ' P/Invokes for native API
  Public Declare Function ShowWindow Lib "Coredll" (ByVal hWnd As IntPtr, ByVal nCmdShow As SW) As Boolean
  Public Declare Function GetDesktopWindow Lib "Coredll" () As IntPtr
  Public Declare Function SetForegroundWindow Lib "Coredll" (ByVal hwnd As IntPtr) As Boolean
  Public Declare Function BringWindowToTop Lib "Coredll" (ByVal hwnd As IntPtr) As Boolean
  <CLSCompliant(False)> Public Declare Function GetWindowThreadProcessId Lib "Coredll" (ByVal hwnd As IntPtr, ByRef lpdwProcessId As UInteger) As Integer
  Public Declare Function GetClassName Lib "Coredll" (ByVal hwnd As IntPtr, ByVal lpClassName As StringBuilder, ByVal nMaxCount As Integer) As Integer
  Public Declare Function GetForegroundWindow Lib "Coredll" () As IntPtr
  <CLSCompliant(False)> Public Declare Function GetWindow Lib "Coredll" (ByVal hwnd As IntPtr, ByVal uCmd As UInteger) As IntPtr
  Public Declare Function IsWindowVisible Lib "coredll" (ByVal hwnd As IntPtr) As Boolean

  ' Enums for the P/Invokes
  Enum SW As Int32
    Hide = 0
    Show = 5
    ShowNA = 8
    ShowNormal = 1
  End Enum

  Enum GW As Int32
    CHILD = 5
    HWNDFIRST = 0
    HWNDLAST = 1
    HWNDNEXT = 2
    HWNDPREV = 3
    OWNER = 4
  End Enum



  ' Handles the "load" event
  Private Sub TFDetachSvc_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
    ' Check to see if Manila is enabled in the today items
    If TFDRegistrySettings.TF3DTodayEnabled OrElse TFDRegistrySettings.TF2DTodayEnabled Then
      MessageBox.Show("TouchFlo must be disabled in the Today items to use TouchFlo Detacher! Open TouchFlo Detacher Config to fix this.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Hand, MessageBoxDefaultButton.Button1)
      Me.Close()
    End If

    ' Tell the timer to start immediately after form load
    Me.TimerHide.Enabled = True
    ' Me._subclass = New SubClass(Me)
  End Sub

  ' Handles the timer's hide event
  Private Sub TimerHide_Tick(ByVal sender As Object, ByVal e As EventArgs) Handles TimerHide.Tick
    ' Hide the main form so it becomes invisible and will not be considered for closing by the OS
    Me.TimerHide.Enabled = False
    Me.Hide()
    If TFDRegistrySettings.LaunchTouchFloOnStartup Then
      Me.TimerShowDelay.Interval = TFDRegistrySettings.LaunchTouchFloOnStartupDelayInSeconds * 1000
      Me.TimerShowDelay.Enabled = True
    Else
      Me.Close()
    End If
    Me._isLoaded = True
  End Sub

  ' Show manila after delay
  Private Sub TimerShowDelay_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TimerShowDelay.Tick
    Me.TimerShowDelay.Enabled = False
    Me.ShowManila()

    ' If we are supposed to then hide and show manila, do that here
    If TFDRegistrySettings.LaunchTouchFloDialerWorkaround Then
      ' keep looking for manila for 60 seconds (service timeout)
      Dim timeOutCount As Integer = 0
      While timeOutCount < 240
        Dim mp As UInteger = Me.FindManilaPID
        If mp > 0 Then
          Dim mc As IntPtr = Me.FindFirstManilaHwndByClass(TFDRegistrySettings.TouchFloMainWindowClass)
          If mc <> IntPtr.Zero Then
            Dim sb As New StringBuilder
            If IsWindowVisible(mc) Then
              Dim timeOutCount2 As Integer = 0

              Threading.Thread.Sleep(500)
              Me.HideManila()

              While IsWindowVisible(mc) AndAlso timeOutCount2 < 600
                Threading.Thread.Sleep(100)
                timeOutCount2 += 1
              End While

              Threading.Thread.Sleep(500)
              Me.ShowManila()

              Threading.Thread.Sleep(2000)
              Me.HideManila()

              Threading.Thread.Sleep(250)
              Me.ShowManila()

              Exit While
            End If
          End If
        End If

        timeOutCount += 1
        Threading.Thread.Sleep(1000)
      End While
    End If

    Me.LaunchOtherApp()

    Me.Close()
  End Sub

  '' Handles the main form activation event (basically, the app was run again)
  'Private Sub TFDetachSvc_Show(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Activated
  '  If Me._isLoaded Then
  '    Me._isLoaded = False ' prevent loop repeats
  '    ' If the form ever receives activation, exit.  This is usually only possible by running the EXE again
  '    MessageBox.Show("Closing TouchFlo Detacher Service", "Closing...", MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1)
  '    Me.Close()
  '  End If
  'End Sub

  '' When the subclass sees a special message, it will pass it to this method to handle
  'Public Sub HandleWindowMessage(ByVal wparam As Int32, ByVal lparam As Int32)
  '  If wparam = 1 Then
  '    If lparam = 1 Then
  '      Me.ShowManila()
  '    ElseIf lparam = 2 Then
  '      Me.HideManila()
  '    ElseIf lparam = 3 Then
  '      Me.CloseManila()
  '    ElseIf lparam = 4 Then
  '      Me.ToggleManila()
  '    End If
  '  End If
  'End Sub

  Private Sub LaunchOtherApp()
    If TFDRegistrySettings.LaunchAppAfterTouchFlo Then
      Dim sec As Integer = TFDRegistrySettings.LaunchAppAfterTouchFloDelayInSeconds * 1000
      Threading.Thread.Sleep(sec)
      Try
        Dim p As New Process
        p.StartInfo.FileName = TFDRegistrySettings.LaunchAppAfterTouchFloPath
        p.Start()
      Catch ex As Exception
        Windows.Forms.Clipboard.SetDataObject(ex.ToString)
        Me.ShowErr("Unable to start process: " & TFDRegistrySettings.LaunchAppAfterTouchFloPath & vbCrLf & vbCrLf & "Exception details have been copied to the clipboard!")
      End Try
    End If
  End Sub

  ' Show Manila! (or launch it if it is not running)
  Private Sub ShowManila()
    ' Show the current desktop
    SetForegroundWindow(GetDesktopWindow())

    ' Find Manila's main window
    Dim hWnd As IntPtr = Me.FindFirstManilaHwndByClass(TFDRegistrySettings.TouchFloMainWindowClass)

    ' If Manila was found...
    If hWnd <> IntPtr.Zero Then
      ' Show Manila's main window
      ShowWindow(hWnd, SW.ShowNormal)

      ' Make sure Manila is top-most in the z-order
      SetForegroundWindow(hWnd)
      BringWindowToTop(hWnd)

      ' Make sure Manila's softkeys are visible
      For Each hWndsk As IntPtr In Me.FindAllManilaHwndByClass(TFDRegistrySettings.TouchFloSoftKeyWindowClass)
        If (hWndsk <> IntPtr.Zero) Then
          ShowWindow(hWndsk, SW.ShowNormal)
        End If
      Next
    Else ' Manila was not found, so start the process
      Try
        Dim p As New Process
        p.StartInfo.FileName = TFDRegistrySettings.TouchFloFullPath
        p.Start()
      Catch ex As Exception
        Windows.Forms.Clipboard.SetDataObject(ex.ToString)
        Me.ShowErr("Unable to start process: " & TFDRegistrySettings.TouchFloFullPath & vbCrLf & vbCrLf & "Exception details have been copied to the clipboard!")
      End Try
    End If
  End Sub

  ' Hide (but do not close) Manila!
  Private Sub HideManila()
    ' Show the current desktop
    SetForegroundWindow(GetDesktopWindow())

    ' Find Manila's main window
    Dim hWnd As IntPtr = Me.FindFirstManilaHwndByClass(TFDRegistrySettings.TouchFloMainWindowClass)

    ' If Manila was found...
    If hWnd <> IntPtr.Zero Then
      ' Hide Manila's softkey panels (all)
      For Each hWndsk As IntPtr In Me.FindAllManilaHwndByClass(TFDRegistrySettings.TouchFloSoftKeyWindowClass)
        If (hWndsk <> IntPtr.Zero) Then
          ShowWindow(hWndsk, SW.Hide)
        End If
      Next

      ' Hide Manila's main window
      ShowWindow(hWnd, SW.Hide)
    Else
      Me.ShowErr("HIDE: Unable to find TouchFlo main window handle.")
    End If

    ' Show the Manilaless desktop
    SetForegroundWindow(GetDesktopWindow())
  End Sub

  ' Close Manila!
  Private Sub CloseManila()
    Dim found As Boolean = False
    Dim exe As String = TFDRegistrySettings.TouchFloEXEOnly.ToLower

    For Each p As ProcessEntry In ProcessEntry.GetProcesses
      If p.ExeFile.ToLower = exe Then
        p.Kill()
        found = True
        Exit For
      End If
    Next

    If Not found Then
      Me.ShowErr("CLOSE: TouchFlo process not found - " & TFDRegistrySettings.TouchFloEXEOnly.ToLower)
    End If

    ' Show the Manilaless desktop
    SetForegroundWindow(GetDesktopWindow())
  End Sub

  ' Find Manila's Process ID
  Private Function FindManilaPID() As UInteger
    Dim exe As String = TFDRegistrySettings.TouchFloEXEOnly.ToLower
    For Each p As ProcessEntry In ProcessEntry.GetProcesses
      If p.ExeFile.ToLower = exe Then
        Return p.ProcessID
      End If
    Next
    Return 0
  End Function

  ' Toggle hide/show
  Private Sub ToggleManila()
    ' Find Manila's main window
    Dim hWnd As IntPtr = Me.FindFirstManilaHwndByClass(TFDRegistrySettings.TouchFloMainWindowClass)

    ' If Manila was found and is visible...
    If hWnd <> IntPtr.Zero AndAlso IsWindowVisible(hWnd) Then
      HideManila()
    Else ' otherwise, show it!
      ShowManila()
    End If
  End Sub


  ' Find the HWND to a class in the manila process
  Private Function FindFirstManilaHwndByClass(ByVal windowClass As String) As IntPtr
    Dim wclass As String = windowClass.ToLower
    Dim mpid As UInteger = FindManilaPID()

    If mpid > 0 Then
      Dim hwnd As IntPtr = GetForegroundWindow()
      hwnd = GetWindow(hwnd, GW.HWNDFIRST)

      While hwnd <> IntPtr.Zero
        Dim className As New StringBuilder(20)
        GetClassName(hwnd, className, 20)
        If className.ToString.ToLower = wclass Then
          Dim pid As UInteger = 0
          GetWindowThreadProcessId(hwnd, pid)
          If pid = mpid Then
            Return hwnd
          End If
        End If
        hwnd = GetWindow(hwnd, GW.HWNDNEXT)
      End While
    End If

    Return IntPtr.Zero
  End Function

  ' Find the HWND to a class in the manila process
  Private Function FindAllManilaHwndByClass(ByVal windowClass As String) As List(Of IntPtr)
    FindAllManilaHwndByClass = New List(Of IntPtr)

    Dim wclass As String = windowClass.ToLower
    Dim mpid As UInteger = FindManilaPID()

    If mpid > 0 Then
      Dim hwnd As IntPtr = GetForegroundWindow()
      hwnd = GetWindow(hwnd, GW.HWNDFIRST)

      While hwnd <> IntPtr.Zero
        Dim className As New StringBuilder(20)
        GetClassName(hwnd, className, 20)
        If className.ToString.ToLower = wclass Then
          Dim pid As UInteger = 0
          GetWindowThreadProcessId(hwnd, pid)
          If pid = mpid Then
            FindAllManilaHwndByClass.Add(hwnd)
          End If
        End If
        hwnd = GetWindow(hwnd, GW.HWNDNEXT)
      End While
    End If
  End Function

  ' Method to handle showing service errors
  Private Sub ShowErr(ByVal message As String)
    If TFDRegistrySettings.ShowServiceErrors Then
      Me._isLoaded = False ' do this so TFDSvc doesn't quit when focus returns after the messagebox is displayed

      MessageBox.Show(message, "TFDetachSvc Error", MessageBoxButtons.OK, MessageBoxIcon.Hand, MessageBoxDefaultButton.Button1)

      Me.SendToBack()
      Me._isLoaded = True
    End If
  End Sub

End Class
